#include "core/main.h"

namespace MissileSwarmConstants
{
    static const int boardWidth = 20;
    static const int boardHeight = 20;

    static const bool spawnCandy = false;
    static const bool spawnMissiles = true;
    static const bool missileBlocks = true;
    
    static const int numMissileBlocks = 5;

    static const int candySpawnRate = 100;
    static const int missileSpawnRate = 50;
    static const int missileSpawnIncrement = 0;

    static const int appleReward = 10;
    static const int deathPenalty = 1000;
    static const int missileDodgeReward = 0;
}

void GameMissileSwarm::Init()
{
    _spriteAlien        = _collection.AddSprite(Bitmap::LoadFile("../assets/missileSwarm/alien.png"), "alien");
    _spriteCandyStart   = _collection.AddSprite(Bitmap::LoadFile("../assets/missileSwarm/candyStart.png"), "candyStart");
    _spriteCandy        = _collection.AddSprite(Bitmap::LoadFile("../assets/missileSwarm/candy.png"), "candy");
    _spriteMissileStart = _collection.AddSprite(Bitmap::LoadFile("../assets/missileSwarm/missileStart.png"), "missileStart");
    _spriteMissile      = _collection.AddSprite(Bitmap::LoadFile("../assets/missileSwarm/missile.png"), "missile");
    _spriteShip         = _collection.AddSprite(Bitmap::LoadFile("../assets/missileSwarm/ship.png"), "ship");
    
    //
    // Font sprites should always be the last thing added (so that it's easiest to ignore them when building feature vectors)
    //
    _collection.AddFontSprites();

    _observedIntNames.push_back("New Game");
    _observedIntNames.push_back("Score");
    _observedIntNames.push_back("Frame Score");
    _observedIntNames.push_back("Level");
    _observedIntNames.push_back("Ship X");
    _observedIntNames.push_back("Frames since new game");
    _observedIntNames.push_back("Apples since new game");

    
    _tiles.Allocate(MissileSwarmConstants::boardHeight, MissileSwarmConstants::boardWidth);

    _activeFrame.observedInts.resize(ObservedIntNames().size());
}

void GameMissileSwarm::NewGame()
{
    for(UINT y = 0; y < _tiles.Rows(); y++)
    {
        for(UINT x = 0; x < _tiles.Cols(); x++)
        {
            if(y == 0)
            {
                _tiles(y, x) = _spriteAlien;
            }
            else
            {
                _tiles(y, x) = -1;
            }
        }
    }
    
    _shipX = MissileSwarmConstants::boardWidth / 2;
    _newGame = 1;
    _level = 1;
    _score = 0;
    _applesEatenSinceReset = 0;
    _framesSinceReset = 0;
}

int GameMissileSwarm::getSprite(const Vec2i &pos)
{
    if(_tiles.ValidCoordinates(pos.y, pos.x)) return _tiles(pos.y, pos.x);
    return _spriteMissile;
}

int GameMissileSwarm::spriteValue(int spriteID)
{
    if(spriteID == _spriteMissile) return -1;
    if(spriteID == _spriteCandy) return 1;
    return 0;
}

ControllerState GameMissileSwarm::SimpleAI()
{
    ControllerState result;

    int leftValue   = spriteValue(getSprite(Vec2i(_shipX - 1, MissileSwarmConstants::boardHeight - 2)));
    int centerValue = spriteValue(getSprite(Vec2i(_shipX, MissileSwarmConstants::boardHeight - 2)));
    int rightValue  = spriteValue(getSprite(Vec2i(_shipX + 1, MissileSwarmConstants::boardHeight - 2)));

    int bestValue = Math::Max(leftValue, centerValue, rightValue);

    bool decisionMade = false;
    while(!decisionMade)
    {
        int r = rand() % 3;
        if(r == 0 && leftValue == bestValue) { result.KeyDown('a'); decisionMade = true; }
        if(r == 1 && rightValue == bestValue) { result.KeyDown('d'); decisionMade = true; }
        if(r == 2 && centerValue == bestValue) { decisionMade = true; }
    }

    return result;
}

void GameMissileSwarm::Step(ControllerState &controller)
{
    int frameScore = 0;

    if(controller.left() ) _shipX--;
    if(controller.right()) _shipX++;

    _shipX = Math::Bound(_shipX, 0, MissileSwarmConstants::boardWidth - 1);

    //
    // All objects drop one square
    //
    for(int y = _tiles.Rows() - 1; y >= 2; y--)
    {
        for(int x = 0; x < int(_tiles.Cols()); x++)
        {
            _tiles(y, x) = _tiles(y - 1, x);
            if(_tiles(y, x) == _spriteCandyStart) _tiles(y, x) = _spriteCandy;
            if(_tiles(y, x) == _spriteMissileStart) _tiles(y, x) = _spriteMissile;
        }
    }

    //
    // New objects spawn
    //
    for(UINT x = 0; x < _tiles.Cols(); x++)
    {
        _tiles(1, x) = -1;

        if (MissileSwarmConstants::spawnCandy) {
            int randomValue = rand() % MissileSwarmConstants::candySpawnRate;
            if(randomValue == 0)
            {
                _tiles(1, x) = _spriteCandyStart;
            }
        }
        
        if (MissileSwarmConstants::spawnMissiles && !MissileSwarmConstants::missileBlocks) {
            int modVal = MissileSwarmConstants::missileSpawnRate;
            modVal -= MissileSwarmConstants::missileSpawnIncrement * (_level - 1);
            int randomValue = rand() % modVal;
            if (randomValue == 0) {
                _tiles(1, x) = _spriteMissileStart;
            }
        }
    }

    if (MissileSwarmConstants::spawnMissiles && MissileSwarmConstants::missileBlocks) {
        if (rand() % MissileSwarmConstants::missileSpawnRate == 0) {
            int missileBlock = rand() % MissileSwarmConstants::numMissileBlocks;
            int blockWidth = MissileSwarmConstants::boardWidth / MissileSwarmConstants::numMissileBlocks;
            int startX = blockWidth * missileBlock;
            int endX = blockWidth * (missileBlock + 1);
            for (int x = startX; x < endX; x++) {
                _tiles(1, x) = _spriteMissileStart;
            }
        }
    }

    for (int x = 0; x < MissileSwarmConstants::boardWidth; x++) {
        int tile = _tiles(MissileSwarmConstants::boardHeight - 1, x);
        if (x != _shipX && tile == _spriteMissile) {
            frameScore += MissileSwarmConstants::missileDodgeReward;
        }
    }

    int shipTile = _tiles(MissileSwarmConstants::boardHeight - 1, _shipX);
    _tiles(MissileSwarmConstants::boardHeight - 1, _shipX) = -1;
    
    if(shipTile == _spriteCandy)
    {
        frameScore += MissileSwarmConstants::appleReward;
        _applesEatenSinceReset++;
        if(_score % 10 == 0)
        {
            _level++;
        }
    }
    else if(shipTile == _spriteMissile)
    {
        frameScore -= MissileSwarmConstants::deathPenalty;
        _newGame = 1;
    }

    _score += frameScore;
    _framesSinceReset++;
    
    _activeFrame.observedInts[0] = _newGame;
    _activeFrame.observedInts[1] = _score;
    _activeFrame.observedInts[2] = frameScore;
    _activeFrame.observedInts[3] = _level;
    _activeFrame.observedInts[4] = _shipX;
    _activeFrame.observedInts[5] = _framesSinceReset;
    _activeFrame.observedInts[6] = _applesEatenSinceReset;

    if (_newGame) {
        NewGame();
    }

    _newGame = 0;
    MakeFrameSprites();
}

void GameMissileSwarm::MakeFrameSprites()
{
    _activeFrame.sprites.clear();
    for(UINT y = 0; y < _tiles.Rows(); y++)
    {
        for(UINT x = 0; x < _tiles.Cols(); x++)
        {
            int curTile = _tiles(y, x);
            if(curTile >= 0) _activeFrame.sprites.push_back(SpriteLocation(Vec2i(x, y) * 8, curTile, 0));
        }
    }

    _activeFrame.sprites.push_back(SpriteLocation(Vec2i(_shipX, MissileSwarmConstants::boardHeight - 1) * 8, _spriteShip, 0));

    _collection.DrawText("SCORE", Vec2i(MissileSwarmConstants::boardWidth + 1, 2), _activeFrame.sprites, 0);
    _collection.DrawNumber(_score, 6, Vec2i(MissileSwarmConstants::boardWidth + 1, 3), _activeFrame.sprites, 0);

    _collection.DrawText("LEVEL", Vec2i(MissileSwarmConstants::boardWidth + 1, 5), _activeFrame.sprites, 0);
    _collection.DrawNumber(_level, 2, Vec2i(MissileSwarmConstants::boardWidth + 1, 6), _activeFrame.sprites, 0);
}
